{"cells":[{"cell_type":"markdown","metadata":{"id":"3rF2GilHM0gd"},"source":["# 2D cell segmentation with cellpose\n","\n","\n","* **Make sure GPU is selected in Colab notebook under \"Edit\" &rarr; \"Notebook settings\".**\n","* Modified by Tim Cheung from [this Colab notebook](https://colab.research.google.com/github/MouseLand/cellpose/blob/master/notebooks/run_cellpose_GPU.ipynb).\n","* For full cellpose documentation, see http://www.cellpose.org/docs\n","* Please cite ***cellpose*** algorithm from [Stringer *et al.* (2020) Nature Method](https://www.nature.com/articles/s41592-020-01018-x). \n","* Cellpose package repo is at https://github.com/MouseLand/cellpose.\n","* See also README at https://github.com/MouseLand/cellpose for more Colab notebook implementations, including one that can train the model with your own data set.\n","* For importing ROI txt into ImageJ, download \"imagej_roi_converter.py\" from cellpose's GitHub (or right click [here](https://github.com/MouseLand/cellpose/raw/master/imagej_roi_converter.py) &rarr; \"save link as\"). With your image already open in ImageJ, run the above file as a macro in ImageJ (it should automatically download Jython), then open the corresponding ROI txt file. See [here](https://cellpose.readthedocs.io/en/latest/outputs.html#roi-manager-compatible-output-for-imagej) for details.\n","* GitHub [link](https://github.com/thccheung/colab-nb-cellpose) to this notebook.\n"]},{"cell_type":"markdown","metadata":{"id":"QO7vw3P6NgJP"},"source":["# Setup"]},{"cell_type":"markdown","metadata":{"id":"46Y2hi14dq6a"},"source":["## Cellpose installation"]},{"cell_type":"markdown","metadata":{"id":"QaoW2Y9VN-XJ"},"source":["Install cellpose -- by default the torch GPU version is installed in COLAB notebook.\n","\n","**Note that cellpose uses the latest version of numpy, so please click the \"Restart runtime\" button once the install completes, if needed.**"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"6uPvjajZOCpt"},"outputs":[],"source":["!pip install \"opencv-python-headless<4.3\"\n","!pip install cellpose"]},{"cell_type":"markdown","metadata":{"id":"N8j5PTSzNtGH"},"source":["Check CUDA version and GPU"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"eTT1cGLdLKlY"},"outputs":[],"source":["!nvcc --version\n","!nvidia-smi"]},{"cell_type":"markdown","metadata":{"id":"dQ29iKX2OFe3"},"source":["Import libraries and check GPU (the first time you import cellpose the models will download)."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"DGCpoV7xOLcD"},"outputs":[],"source":["# Cellpose-related\n","\n","import numpy as np\n","import time, os, sys\n","from urllib.parse import urlparse\n","import skimage.io\n","import matplotlib.pyplot as plt\n","import matplotlib as mpl\n","%matplotlib inline\n","mpl.rcParams['figure.dpi'] = 300\n","\n","from urllib.parse import urlparse\n","from cellpose import models, core\n","\n","use_GPU = core.use_gpu()\n","print('>>> GPU activated? %d'%use_GPU)\n","\n","# call logger_setup to have output of cellpose written\n","from cellpose.io import logger_setup\n","logger_setup();"]},{"cell_type":"markdown","metadata":{"id":"ZRXtrSDCOTJh"},"source":["## Get images"]},{"cell_type":"markdown","metadata":{"id":"Ez-GsaKTOWCt"},"source":["Mount google drive"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"xZEqsRg1Ocga"},"outputs":[],"source":["from google.colab import drive\n","drive.mount(\"/content/drive\", force_remount=True)"]},{"cell_type":"markdown","metadata":{"id":"Mn_mQwC3Oh7-"},"source":["Read images"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"C51guRUg8FRS"},"outputs":[],"source":["import glob\n","from pathlib import Path\n","from cellpose import utils\n","from skimage import io\n","import os\n","\n","##### SETUP OPTIONS #####\n","# Script will take as input: image files that contain a stack of channels, and\n","# then split them to individual channels for downstream cellpose processing\n","\n","img_path = \"/content/drive/My Drive/cellpose/\" # Folder where original images are located (make sure there is a \"/\" at the end of the path )\n","img_proc_path = img_path + \"img_proc/\" # Images are channel-split (if needed) and saved to this folder for segmentation\n","output_path = img_path + \"mask/\" # Output masks (.npy) saved to this folder\n","file_done_dir = img_path + \"file_done/\" # Images processed by cellpose will be moved from \"img_proc_path\" to here. Good for when colab notebook disconnect you half way through.\n","img_orig_ext = \".png\" # Original image's extension\n","img_save_ext = \".png\" # Split channel images saved with this extension\n","\n","split_channel_names = ['ch1', 'ch2', 'ch3', 'ch4'] # Channel names for splitting (used as suffix in saved image files). Ignored if no need to split channels\n","channel_to_segment = ['AF405', 'AF488', 'AF555', 'AF680'] # Channel to segment (needs to be match channel names in file names, or \"split_channel_names\")\n","\n","show_pre_segment_img = False # Flag to show loaded pre-segmented images below (may cost RAM)\n","\n","# cellpose options\n","show_cellpose_outcome_img = True # Flag to show cellpose outcome as image below. Good for diagnostic, but may crash notebook if too many files (out of RAM)\n","cyto_or_nuclei = 'cyto' # cellpose model type, can be 'cyto' or 'nuclei'. 'cyto' seems to work better even for DAPI\n","save_imagej_roi = False # Flag to save masks as text for ImageJ ROI processing\n","roi_txt_path = img_path + \"roi_txt/\" # ROI txt files saved to this folder\n","save_cellprob = False # Flag to save cell probability for each mask as .npy (for diagnostc purposes)\n","cellprob_path = img_path + \"cellprob/\" # Cell probability saved to this folder\n","\n","\n","##### REST OF CODE #####\n","img_files = glob.glob(img_path + \"*\" + img_orig_ext)\n","\n","# split image stack into individual channels if needed\n","Path(img_proc_path).mkdir(exist_ok=True)\n","for k, f in enumerate(img_files):  \n","\n","  img = io.imread(f)  \n","\n","  if len(img.shape) > 2:\n","    n_chn = img.shape[2]\n","    split_chn_flag = True\n","  else:\n","    split_chn_flag = False\n","    n_chn = 1\n","    img = img[..., np.newaxis]\n","\n","  if show_pre_segment_img:\n","    plt.figure(k+1)\n","  \n","  for chn in range(n_chn):\n","    img_chn = img[:, :, chn]\n","    if split_chn_flag:\n","      new_fname = os.path.splitext(os.path.basename(f))[0] + \\\n","      \"_\" + split_channel_names[chn] + img_save_ext\n","    else:\n","      new_fname = os.path.splitext(os.path.basename(f))[0] + img_save_ext\n","    new_fpath = img_proc_path + new_fname\n","    io.imsave(new_fpath, img_chn)\n","\n","    if show_pre_segment_img:\n","      plt.subplot(1, 4, chn + 1)\n","      plt.imshow(img_chn, cmap=\"gray\")\n","\n","# pull out channels to segment\n","img_files = glob.glob(img_proc_path + \"*\" + img_save_ext)\n","imgs = [io.imread(f) for f in img_files]\n","\n","if channel_to_segment == \"all\":\n","  seg_files = img_files\n","else:\n","  def extract_fname(lst):  # onnly pull out file names of correct channel\n","    seg_files = []\n","    for ch in channel_to_segment:\n","      seg_files.extend([el for el in lst if ch in el])\n","    return seg_files\n","  seg_files = extract_fname(img_files)\n","seg_files.sort()\n","print(*seg_files, sep='\\n')\n","\n","imgs = [io.imread(f) for f in seg_files]\n","nimg = len(imgs)\n","print(\"Total \" + str(nimg) + \" images to process.\")"]},{"cell_type":"markdown","metadata":{"id":"FXNNg35kShX_"},"source":["# Run cellpose on 2D images"]},{"cell_type":"markdown","metadata":{"id":"V0cr19Zc-tKK"},"source":["## Setup channel parameters"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"ofFduJ97-91z"},"outputs":[],"source":["import pandas as pd\n","\n","##### SETUP OPTIONS #####\n","# Setup parameters for each channel\n","# Make sure order is correct for each parameter\n","param = {'channel':  ['AF405', 'AF488', 'AF555', 'AF680'],\n","         'cell diam': [12, 16, 20, 14],  # Typical cell diameter, in pixels\n","         'flow threshold': [0.8, 0.6, 0.6, 0.6], # Flow threshold (for cell shape), ranges from 0 (strict) to 1 (relaxed); increase -> more cells segmented (but may increase false positives)\n","         'cellprob threshold': [-3, -1, -1, -1]  # Cell probability threshold, ranges from -6 (relaxed) to 6 (strict); decrease -> more cells segmented (but may increase false positives)\n","         }\n","\n","\n","##### REST OF CODE #####\n","df_channel = pd.DataFrame(param)\n","df_channel.set_index('channel', inplace=True)\n","print(df_channel)"]},{"cell_type":"markdown","metadata":{"id":"3j8vvG6D_HYG"},"source":["## Run cellpose"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"vguko1wySk16"},"outputs":[],"source":["# RUN CELLPOSE\n","\n","##### REST OF CODE #####\n","from cellpose import models, plot, io\n","from shutil import copyfile\n","import os\n","\n","Path(output_path).mkdir(exist_ok=True)  # make directory for output masks\n","Path(file_done_dir).mkdir(exist_ok=True)  # finished files will be moved here\n","if save_imagej_roi:\n","  Path(roi_txt_path).mkdir(exist_ok=True)\n","if save_cellprob:\n","  Path(cellprob_path).mkdir(exist_ok=True)\n","\n","# DEFINE CELLPOSE MODEL\n","# model_type = 'cyto' seems to work better even for Fos. Probably because\n","# cellpose's nuclei model was trained on DAPI-stained, not Fos-stained\n","model = models.Cellpose(gpu=use_GPU, model_type=cyto_or_nuclei) # model_type='cyto' or 'nuclei'\n","\n","\"\"\"\n","# define CHANNELS to run segementation on\n","# grayscale=0, R=1, G=2, B=3\n","# channels = [cytoplasm, nucleus]\n","# if NUCLEUS channel does not exist, set the second channel to 0\n","# channels = [0,0]\n","# IF ALL YOUR IMAGES ARE THE SAME TYPE, you can give a list with 2 elements\n","# channels = [0,0] # IF YOU HAVE GRAYSCALE\n","# channels = [2,3] # IF YOU HAVE G=cytoplasm and B=nucleus\n","# channels = [2,1] # IF YOU HAVE G=cytoplasm and R=nucleus\n","# or if you have different types of channels in each image\n","channels = [[2,3], [0,0], [0,0]]\n","\"\"\"\n","cp_channels = [0, 0]\n","\n","# if diameter is set to None, the size of the cells is estimated on a per image basis\n","# you can set the average cell `diameter` in pixels yourself (recommended) \n","# diameter can be a list or a single number for all images\n","\n","\n","\"\"\"\n","# *** Min size argument in cellpose's model.eval does not seem to work ***\n","minSizeFact = 1.5 # factor for calculating minimum size based on diam\n","minSize = round(minSizeFact * np.pi*(cell_diam/2)**2)\n","\"\"\"\n","\n","for img, f in zip(imgs, seg_files):\n","\n","  img_fname = os.path.splitext(os.path.basename(f))[0] # pull out file name  \n","  channel_flag = False\n","\n","  # Retrieve cellpose parameters from channel dataframe\n","  for chl in df_channel.index:\n","\n","    if chl in img_fname:\n","      cell_diam = df_channel.loc[chl]['cell diam']\n","      flow_threshold = df_channel.loc[chl]['flow threshold']\n","      cellprob_threshold = df_channel.loc[chl]['cellprob threshold']\n","      channel_flag = True\n","    else:\n","      pass\n","\n","  if channel_flag == False:\n","    raise ValueError(\"Channel name not in filename for %s\"  % os.path.basename(f))\n","\n","  # Segment using cellpose\n","  masks, flows, styles, diams = model.eval(img, diameter= cell_diam,\n","                                           normalize = True,\n","                                           flow_threshold=flow_threshold,\n","                                           cellprob_threshold=cellprob_threshold,\n","                                           channels=cp_channels)\n","  \n","  # Save outcome to file  \n","  save_fpath = output_path + img_fname\n","  np.save(save_fpath + '_mask.npy',\n","        masks.astype(np.uint16) if np.max(masks)<2**16-1 else masks.astype(np.uint32))\n","  \n","  # Save masks as text for ImageJ ROI if called\n","  # image_name is file name of image\n","  # masks is numpy array of masks for image\n","  if save_imagej_roi:\n","    base = os.path.splitext(img_fname)[0]\n","    outlines = utils.outlines_list(masks)\n","    io.outlines_to_text(base, outlines)\n","\n","    roi_path = glob.glob(\"/content/\" + base + \"_cp_outlines.txt\")\n","    for i in roi_path:\n","      fname = os.path.basename(i)\n","      copyfile(i, roi_txt_path + fname)\n","  \n","  # Save cell probabilities if called\n","  if save_cellprob:\n","    cellprob = flows[2]\n","    np.save(cellprob_path + img_fname + '_cellprob.npy', cellprob)\n","\n","  print('Finished processing ' + img_fname)\n","\n","  # Move finished file to \"file done\" subdirectory\n","  file_done_path = file_done_dir + os.path.basename(f)\n","  os.replace(f, file_done_path)\n","\n","  # Show outcome (can crash notebok if too many files)\n","  \n","  if show_cellpose_outcome_img:\n","\n","    # Cellpose Plot outcome\n","    \n","    fig = plt.figure(figsize=(12,5))  \n","    plot.show_segmentation(fig, img, masks, flows[0], channels=cp_channels)\n","    plt.tight_layout()\n","    plt.show() \n"]}],"metadata":{"accelerator":"GPU","colab":{"collapsed_sections":[],"name":"run_cellpose_2D_Cheung_et_al.ipynb","provenance":[],"authorship_tag":"ABX9TyNzzkg/HI85QS1J2SrjzNWx"},"kernelspec":{"display_name":"Python 3","name":"python3"}},"nbformat":4,"nbformat_minor":0}